#include "mem/pmem.h"
#include "lib/print.h"
#include "lib/lock.h"
#include "lib/str.h"
#include "memlayout.h"
#include "common.h"

// 物理页节点
typedef struct page_node {
    struct page_node* next;
} page_node_t;

// 许多物理页构成一个可分配的区域
typedef struct alloc_region {
    uint64 begin;          // 起始物理地址
    uint64 end;            // 终止物理地址
    spinlock_t lk;         // 自旋锁(保护下面两个变量)
    uint32 allocable;      // 可分配页面数    
    page_node_t list_head; // 可分配链的链头节点
} alloc_region_t;

// 内核和用户可分配的物理页分开
static alloc_region_t kern_region, user_region;

#define KERN_PAGES 1024 // 内核可分配空间占1024个pages
#define PGSIZE 4096  // 页面大小
#define PGROUNDUP(x) (((x) + PGSIZE - 1) & ~(PGSIZE - 1))

// 物理内存初始化
void pmem_init(void)
{
    // kern_region.begin 和 kern_region.end 设定为内核内存区域的起始和结束地址。
    kern_region.begin=(uint64)ALLOC_BEGIN;
    kern_region.end=(uint64)ALLOC_BEGIN+KERN_PAGES*PGSIZE;

    // 内核上锁 开始初始化
    spinlock_init(&kern_region.lk,"kern_region");
    kern_region.allocable=KERN_PAGES;
    kern_region.list_head.next=NULL;
    user_region.begin=(uint64)ALLOC_BEGIN+KERN_PAGES*PGSIZE;
    user_region.end=(uint64)ALLOC_END;

    // 用户上锁 开始初始化
    spinlock_init(&user_region.lk,"user_region");
    user_region.allocable=(user_region.end-user_region.begin)/PGSIZE;
    user_region.list_head.next = NULL;

    // 为用户和内核区域创建空白页表
    freerange(kern_region.begin,kern_region.end,true);
    freerange(user_region.begin,user_region.end,false);
}

// 返回一个可分配的干净物理页
// 失败则panic锁死
// alloc操作就是移除 list_head->next
void* pmem_alloc(bool in_kernel)
{
    alloc_region_t* ar = in_kernel ? &kern_region : &user_region;

    // 锁定区域
    spinlock_acquire(&ar->lk);

    // 检查当前区域是否存在可分配页面
    if (ar->allocable == 0)
    {
        spinlock_release(&ar->lk);
        panic("page empty wrong!");
    }
    
    // 获取链表头节点
    page_node_t* allocated_page = ar->list_head.next;
    if (allocated_page) {
        // 将链表头移动到下一个节点
        ar->list_head.next = allocated_page->next;
        // 更新可分配页数
        ar->allocable--;
    }

    // 释放锁
    spinlock_release(&ar->lk);

    if (allocated_page) {
        // 如果成功获取到物理页，填充页面内容为 0 
        memset((char*)allocated_page, 0, PGSIZE);
    }

    // 返回分配的物理页地址（或 NULL）
    return (void*)allocated_page;
}


// 释放物理页
// 失败则panic锁死
void pmem_free(uint64 page, bool in_kernel)
{
    alloc_region_t* ar = in_kernel ? &kern_region : &user_region;

    // 检查页地址是否有效
    if ((page % PGSIZE) != 0 || page < ar->begin || page >= ar->end) {
        panic("pmem_free: Invalid page address!");
    }

    spinlock_acquire(&ar->lk);

    // 清空页面内容
    memset((void*)page, 1, PGSIZE);

    // 释放物理页
    page_node_t pa;
    pa.next = (page_node_t*)page; 
    pa.next->next = ar->list_head.next;
    ar->list_head = pa;

    spinlock_release(&ar->lk);
}

void freerange(uint64 begin, uint64 end, bool in_kernel)
{
    char *p;
    // 地址对齐
    p = (char *)PGROUNDUP((uint64)begin);
    // 遍历这个地址范围，对其进行pmem_free操作 清理和重置页面
    for (; p + PGSIZE <= (char *)end; p += PGSIZE)
    pmem_free((uint64)p, in_kernel);
}